#include <map>
#include <sstream>
#include <cstdint>
#include <cstdio>

#include "ft2build.h"
#include FT_FREETYPE_H

#include "../../source/TexturePacker.hpp"

template<typename T>
T StringToNumber(const std::string& numberAsString)
{
	T value;

	std::stringstream stream(numberAsString);
	stream >> value;
	if (stream.fail())
	{
		std::runtime_error e(numberAsString);
		throw e;
	}
	return value;
}

void Serialize(uint16_t data, std::FILE *file)
{
	uint8_t x = (data >> 8) & 0xff;
	std::fwrite(&x, 1, 1, file);
	x = data & 0xff;
	std::fwrite(&x, 1, 1, file);
}

struct CharacterData
{
	uint16_t width, height;
	int16_t offsetX, offsetY;
	int16_t advanceX, advanceY;
	uint16_t textureX, textureY;
	uint8_t *pixels;
};

int main(int argc, char *argv[])
{
	if(argc != 4)
	{
		printf("Usage:\n ft.exe input_font size output_font\n");
		return 1;
	}

	FT_Library library;
	FT_Face face;
	int size;
	std::map<uint16_t, CharacterData> characters;

	size = StringToNumber<int>(argv[2]);
	if(size < 0)
	{
		printf("Invalid font size\n");
		return 1;
	}

	if(FT_Init_FreeType(&library) != 0)
	{
		printf("Couldn't initialize freetype2\n");
		return 1;
	}

	if(FT_New_Face(library, argv[1], 0, &face) != 0)
	{
		printf("Couldn't load font\n");
		return 1;
	}

	if(FT_Set_Pixel_Sizes(face, 0, size) != 0)
	{
		printf("Couldn't set font pixel size\n");
		return 1;
	}

	// todo: character ranges need to be specified externally

	for(int i=32; i<127; i++)
	{
		int glyphIndex = FT_Get_Char_Index(face, i);
		if(FT_Load_Glyph(face, glyphIndex, 0) != 0)
		{
			printf("Couldn't load glyph\n");
			return 1;
		}
		if(FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL) != 0)
		{
			printf("Couldn't render glyph\n");
			return 1;
		}

		CharacterData characterData;
		characterData.offsetX = face->glyph->bitmap_left;
		characterData.offsetY = face->glyph->bitmap_top;
		characterData.advanceX = static_cast<uint16_t>(face->glyph->advance.x / 64);
		characterData.advanceY = static_cast<uint16_t>(face->glyph->advance.y / 64);
		characterData.width = face->glyph->bitmap.width;
		characterData.height = face->glyph->bitmap.rows;
		characterData.pixels = new unsigned char[face->glyph->bitmap.width * face->glyph->bitmap.rows];
		memcpy(characterData.pixels, face->glyph->bitmap.buffer, face->glyph->bitmap.width * face->glyph->bitmap.rows);

		characters[i] = characterData;
	}

	fge::TexturePacker texturePacker(1);

	for(std::map<uint16_t, CharacterData>::iterator iterator = characters.begin(); iterator != characters.end(); ++iterator)
	{
		texturePacker.AddTexture(iterator->second.width, iterator->second.height);
	}

	int textureWidth, textureHeight;
	texturePacker.PackTextures(textureWidth, textureHeight);

	uint8_t *pixels = new uint8_t[textureWidth * textureHeight];
	memset(pixels, 0, textureWidth * textureHeight);

	std::FILE *file = std::fopen(argv[3], "wb");

	Serialize(1, file);
	Serialize(size, file);
	Serialize(textureWidth, file);
	Serialize(textureHeight, file);
	Serialize(characters.size(), file);

	int index = 0;
	for(std::map<uint16_t, CharacterData>::iterator iterator = characters.begin(); iterator != characters.end(); ++iterator)
	{
		int tX, tY, tW, tH;
		texturePacker.GetDataByIndex(index++, tX, tY, tW, tH);
		iterator->second.textureX = tX;
		iterator->second.textureY = tY;
		if(iterator->second.width != tW || iterator->second.height != tH)
			printf("Unexpected dimension difference in character %c\n", iterator->first);

		for(int y=0; y<iterator->second.height; y++)
		{
			for(int x=0; x<iterator->second.width; x++)
			{
				pixels[(tY + y) * textureWidth + (tX + x)] = iterator->second.pixels[y * iterator->second.width + x];
			}
		}

		Serialize(iterator->first, file);
		Serialize(tW, file);
		Serialize(tH, file);
		Serialize(iterator->second.offsetX, file);
		Serialize(iterator->second.offsetY, file);
		Serialize(iterator->second.advanceX, file);
		Serialize(iterator->second.advanceY, file);
		Serialize(tX, file);
		Serialize(tY, file);
	}

	std::fwrite(pixels, 1, textureWidth * textureHeight, file);
	std::fclose(file);

	// version (uint16_t)
	// font size (uint16_t)
	// texture width (uint16_t)
	// texture height (uint16_t)
	// number of characters (uint16_t)
	// foreach character
	//		uint16_t character
	//		uint16_t width, height;
	//		int16_t offsetX, offsetY;
	//		int16_t advanceX, advanceY;
	//		uint16_t textureX, textureY;
	// texture (array of uint8_t)

	delete[] pixels;

	for(std::map<uint16_t, CharacterData>::iterator iterator = characters.begin(); iterator != characters.end(); ++iterator)
	{
		delete[] (*iterator).second.pixels;
	}

	return 0;
}
